/**********
This library is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 3 of the License, or (at your
option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)

This library is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
more details.

You should have received a copy of the GNU Lesser General Public License
along with this library; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
**********/
// "liveMedia"
// Copyright (c) 1996-2018 Live Networks, Inc.  All rights reserved.
// RTP sink for Raw video
// Implementation

#include "include/RawVideoRTPSink.hh"

RawVideoRTPSink *RawVideoRTPSink
::createNew(UsageEnvironment &env, Groupsock *RTPgs, u_int8_t rtpPayloadFormat,
            unsigned height, unsigned width, unsigned depth,
            char const *sampling, char const *colorimetry) {
    return new RawVideoRTPSink(env, RTPgs,
                               rtpPayloadFormat,
                               height, width, depth,
                               sampling, colorimetry);
}

RawVideoRTPSink
::RawVideoRTPSink(UsageEnvironment &env, Groupsock *RTPgs, u_int8_t rtpPayloadFormat,
                  unsigned height, unsigned width, unsigned depth,
                  char const *sampling, char const *colorimetry)
        : VideoRTPSink(env, RTPgs, rtpPayloadFormat, 90000, "RAW"),
          fFmtpSDPLine(NULL), fSampling(NULL), fWidth(width), fHeight(height),
          fDepth(depth), fColorimetry(NULL), fLineindex(0) {

    // Then use this 'config' string to construct our "a=fmtp:" SDP line:
    unsigned fmtpSDPLineMaxSize = 200;// 200 => more than enough space
    fFmtpSDPLine = new char[fmtpSDPLineMaxSize];
    sprintf(fFmtpSDPLine, "a=fmtp:%d sampling=%s;width=%u;height=%u;depth=%u;colorimetry=%s\r\n",
            rtpPayloadType(), sampling, width, height, depth, colorimetry);

    // Set parameters
    fSampling = strDup(sampling);
    fColorimetry = strDup(colorimetry);
    setFrameParameters();
}

RawVideoRTPSink::~RawVideoRTPSink() {
    delete[] fFmtpSDPLine;
    delete[] fSampling;
    delete[] fColorimetry;
}

char const *RawVideoRTPSink::auxSDPLine() {
    return fFmtpSDPLine;
}

void RawVideoRTPSink
::doSpecialFrameHandling(unsigned fragmentationOffset,
                         unsigned char *frameStart,
                         unsigned numBytesInFrame,
                         struct timeval framePresentationTime,
                         unsigned numRemainingBytes) {

    unsigned *lengths = NULL;
    unsigned *offsets = NULL;
    unsigned nbLines = getNbLineInPacket(fragmentationOffset, lengths, offsets);
    unsigned specialHeaderSize = 2 + (6 * nbLines);
    u_int8_t *specialHeader = new u_int8_t[specialHeaderSize];

    // Extended Sequence Number (not used)
    specialHeader[0] = 0;
    specialHeader[1] = 0;

    for (unsigned i = 0; i < nbLines; i++) {
        // detection of new line incrementation
        if ((offsets[i] == 0) && fragmentationOffset != 0) {
            fLineindex = fLineindex + fFrameParameters.scanLineIterationStep;
        }

        // Set length
        specialHeader[2 + (i * 6) + 0] = lengths[i] >> 8;
        specialHeader[2 + (i * 6) + 1] = (u_int8_t) lengths[i];

        // Field Identification (false for us)
        bool fieldIdent = false;

        // Set line index
        specialHeader[2 + (i * 6) + 2] = ((fLineindex >> 8) & 0x7F) | (fieldIdent << 7);
        specialHeader[2 + (i * 6) + 3] = (u_int8_t) fLineindex;

        // Set Continuation bit
        bool continuationBit = (i < nbLines - 1) ? true : false;

        // Set offset
        specialHeader[2 + (i * 6) + 4] = ((offsets[i] >> 8) & 0x7F) | (continuationBit << 7);
        specialHeader[2 + (i * 6) + 5] = (u_int8_t) offsets[i];
    }

    setSpecialHeaderBytes(specialHeader, specialHeaderSize);

    if (numRemainingBytes == 0) {
        // This packet contains the last (or only) fragment of the frame.
        // Set the RTP 'M' ('marker') bit:
        setMarkerBit();
        // Reset line index
        fLineindex = 0;
    }

    // Also set the RTP timestamp:
    setTimestamp(framePresentationTime);

    delete[] specialHeader;
    delete[] lengths;
    delete[] offsets;
}

Boolean RawVideoRTPSink::frameCanAppearAfterPacketStart(unsigned char const * /*frameStart*/,
                                                        unsigned /*numBytesInFrame*/) const {
    // Only one frame per packet:
    return False;
}

unsigned RawVideoRTPSink::specialHeaderSize() const {
    unsigned *lengths = NULL;
    unsigned *offsets = NULL;
    unsigned nbLines = getNbLineInPacket(curFragmentationOffset(), lengths, offsets);
    delete[] lengths;
    delete[] offsets;
    return 2 + (6 * nbLines);
}

unsigned RawVideoRTPSink::getNbLineInPacket(unsigned fragOffset, unsigned *&lengths,
                                            unsigned *&offsets) const {
    unsigned rtpHeaderSize = 12;
    unsigned specialHeaderSize = 2; // Extended Sequence Nb
    unsigned packetMaxSize = ourMaxPacketSize();
    unsigned nbLines = 0;
    unsigned remainingSizeInPacket;

    if (fragOffset >= fFrameParameters.frameSize) {
        envir() << "RawVideoRTPSink::getNbLineInPacket(): bad fragOffset " << fragOffset << "\n";
        return 0;
    }
    unsigned lengthArray[100] = {0};
    unsigned offsetArray[100] = {0};
    unsigned curDataTotalLength = 0;
    unsigned lineOffset = (fragOffset % fFrameParameters.scanLineSize);

    unsigned remainingLineSize =
            fFrameParameters.scanLineSize - (fragOffset % fFrameParameters.scanLineSize);
    while (1) {
        if (packetMaxSize - specialHeaderSize - rtpHeaderSize - 6 <= curDataTotalLength) {
            break; // packet sanity check
        }

        // add one line
        nbLines++;
        specialHeaderSize += 6;

        remainingSizeInPacket =
                packetMaxSize - specialHeaderSize - rtpHeaderSize - curDataTotalLength;
        remainingSizeInPacket -=
                remainingSizeInPacket % fFrameParameters.pGroupSize; // use only multiple of pgroup
        lengthArray[nbLines - 1] = remainingLineSize < remainingSizeInPacket ? remainingLineSize
                                                                             : remainingSizeInPacket;
        offsetArray[nbLines - 1] =
                lineOffset * fFrameParameters.scanLineIterationStep / fFrameParameters.pGroupSize;
        if (remainingLineSize >= remainingSizeInPacket) {
            break; //packet full
        }

        remainingLineSize = fFrameParameters.scanLineSize;
        curDataTotalLength += lengthArray[nbLines - 1];
        lineOffset = 0;

        if (fragOffset + curDataTotalLength >= fFrameParameters.frameSize) {
            break; // end of the frame.
        }
    }

    lengths = new unsigned[nbLines];
    offsets = new unsigned[nbLines];
    for (unsigned i = 0; i < nbLines; i++) {
        lengths[i] = lengthArray[i];
        offsets[i] = offsetArray[i];
    }
    return nbLines;
}

unsigned RawVideoRTPSink::computeOverflowForNewFrame(unsigned newFrameSize) const {
    unsigned initialOverflow = MultiFramedRTPSink::computeOverflowForNewFrame(newFrameSize);

    // Adjust (increase) this overflow to be a multiple of the pgroup value
    unsigned numFrameBytesUsed = newFrameSize - initialOverflow;
    initialOverflow += numFrameBytesUsed % fFrameParameters.pGroupSize;

    return initialOverflow;
}

void RawVideoRTPSink::setFrameParameters() {
    fFrameParameters.scanLineIterationStep = 1;
    if ((strncmp("RGB", fSampling, strlen(fSampling)) == 0) ||
        (strncmp("BGR", fSampling, strlen(fSampling)) == 0)) {
        switch (fDepth) {
            case 8:
                fFrameParameters.pGroupSize = 3;
                fFrameParameters.nbOfPixelInPGroup = 1;
                break;
            case 10:
                fFrameParameters.pGroupSize = 15;
                fFrameParameters.nbOfPixelInPGroup = 4;
                break;
            case 12:
                fFrameParameters.pGroupSize = 9;
                fFrameParameters.nbOfPixelInPGroup = 2;
                break;
            case 16:
                fFrameParameters.pGroupSize = 6;
                fFrameParameters.nbOfPixelInPGroup = 1;
                break;
            default:
                break;
        }
    } else if ((strncmp("RGBA", fSampling, strlen(fSampling)) == 0) ||
               (strncmp("BGRA", fSampling, strlen(fSampling)) == 0)) {
        switch (fDepth) {
            case 8:
                fFrameParameters.pGroupSize = 4;
                break;
            case 10:
                fFrameParameters.pGroupSize = 5;
                break;
            case 12:
                fFrameParameters.pGroupSize = 6;
                break;
            case 16:
                fFrameParameters.pGroupSize = 8;
                break;
            default:
                break;
        }
        fFrameParameters.nbOfPixelInPGroup = 1;
    } else if (strncmp("YCbCr-4:4:4", fSampling, strlen(fSampling)) == 0) {
        switch (fDepth) {
            case 8:
                fFrameParameters.pGroupSize = 3;
                fFrameParameters.nbOfPixelInPGroup = 1;
                break;
            case 10:
                fFrameParameters.pGroupSize = 15;
                fFrameParameters.nbOfPixelInPGroup = 4;
                break;
            case 12:
                fFrameParameters.pGroupSize = 9;
                fFrameParameters.nbOfPixelInPGroup = 2;
                break;
            case 16:
                fFrameParameters.pGroupSize = 6;
                fFrameParameters.nbOfPixelInPGroup = 1;
                break;
            default:
                break;
        }
    } else if (strncmp("YCbCr-4:2:2", fSampling, strlen(fSampling)) == 0) {
        switch (fDepth) {
            case 8:
                fFrameParameters.pGroupSize = 4;
                break;
            case 10:
                fFrameParameters.pGroupSize = 5;
                break;
            case 12:
                fFrameParameters.pGroupSize = 6;
                break;
            case 16:
                fFrameParameters.pGroupSize = 8;
                break;
            default:
                break;
        }
        fFrameParameters.nbOfPixelInPGroup = 2;
    } else if (strncmp("YCbCr-4:1:1", fSampling, strlen(fSampling)) == 0) {
        switch (fDepth) {
            case 8:
                fFrameParameters.pGroupSize = 6;
                break;
            case 10:
                fFrameParameters.pGroupSize = 15;
                break;
            case 12:
                fFrameParameters.pGroupSize = 9;
                break;
            case 16:
                fFrameParameters.pGroupSize = 12;
                break;
            default:
                break;
        }
        fFrameParameters.nbOfPixelInPGroup = 4;
    } else if (strncmp("YCbCr-4:2:0", fSampling, strlen(fSampling)) == 0) {
        switch (fDepth) {
            case 8:
                fFrameParameters.pGroupSize = 6;
                break;
            case 10:
                fFrameParameters.pGroupSize = 15;
                break;
            case 12:
                fFrameParameters.pGroupSize = 9;
                break;
            case 16:
                fFrameParameters.pGroupSize = 12;
                break;
            default:
                break;
        }
        fFrameParameters.nbOfPixelInPGroup = 4;
        fFrameParameters.scanLineIterationStep = 2;
    }
    fFrameParameters.frameSize =
            fHeight * fWidth * fFrameParameters.pGroupSize / fFrameParameters.nbOfPixelInPGroup;
    fFrameParameters.scanLineSize =
            fWidth * fFrameParameters.pGroupSize / fFrameParameters.nbOfPixelInPGroup *
            fFrameParameters.scanLineIterationStep;
}
